package com.example.tree.service.impl;

import com.example.common.domain.PageResult;
import com.example.common.domain.Result;
import com.example.tree.domain.dto.TreeDTO;
import com.example.tree.domain.dto.TreePageDTO;
import com.example.tree.domain.po.AncientTree;
import com.example.tree.domain.po.TreeMedia;
import com.example.tree.domain.vo.*;
import com.example.tree.mapper.TreeMediaMapper;
import com.example.tree.mapper.TreeMapper;
import com.example.tree.service.TreeService;
import com.example.tree.utils.GenerateQrCodeUtil;
import com.example.upload.api.UploadClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.zxing.WriterException;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.BooleanUtils;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class TreeServiceImpl implements TreeService {
    private final TreeMapper treeMapper;
    private final UploadClient uploadClient;
    private final TreeMediaMapper treeDetailMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    private final BloomFilterService bloomFilterService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 添加古树信息
     *
     * @param treeDTO
     */
    @Transactional
    @Override
    public void addTreeInfo(TreeDTO treeDTO) throws IOException, WriterException {
        AncientTree ancientTree = new AncientTree();
        // 属性复制
        BeanUtils.copyProperties(treeDTO, ancientTree);
        ancientTree.setCreateTime(LocalDateTime.now());
        ancientTree.setUpdateTime(LocalDateTime.now());
        // 插入基本信息，不包含图片，以及二维码图片
        treeMapper.insert(ancientTree);

        // 生成二维码中需要存储的信息 ----URL
        // 扫描以后就跳转到对应的页面
        //String qrCodeContent = "http://localhost:8120/ancientTree?treeId=" + ancientTree.getTreeId();
        String qrCodeContent =  ancientTree.getTreeId().toString();
        BufferedImage qrCode = GenerateQrCodeUtil.getQrCode(qrCodeContent);
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {

            // 使用 png 格式写入图像
            ImageIO.write(qrCode, "png", os);
            byte[] bytes = os.toByteArray();
            // 图片放在qrCode下，名称为treeId.png
            String path = "qrCode/" + ancientTree.getTreeId() + ".png";
            String result = uploadClient.uploadFiles(bytes, path);
            // 将 JSON 字符串 result 转换成 Result 对象
            Result resultObject = objectMapper.readValue(result, Result.class);
            treeMapper.updateQrCode((String)resultObject.getData(), ancientTree.getTreeId());
        }
        List<String> urls = treeDTO.getImages();

        treeMapper.insertImages(ancientTree.getTreeId(), urls);
        // 删除Redis中的古树数据
        // 使用SCAN安全模式（避免KEYS阻塞）
        String pattern = "tree:*";
        Set<String> keys = redisTemplate.keys(pattern);
        if (keys != null && !keys.isEmpty())  {
            redisTemplate.delete(keys);
        }

    }

    /**
     * 获取区域古树位置信息
     *
     * @param level 区域级别
     * @param code  区域编码
     * @return
     */
    @Override
    public List<MarkersVO> getTreeMarkers(String level, String code) {
        String key="tree:"+level+":"+code;
        // 1. 判断布隆滤器中是否包含该数据
        if (!bloomFilterService.mightContain(key)) {
            // 不存在，返回空
            return new ArrayList<>();
        }
        // 存在，尝试从redis中获取数据
        List<MarkersVO> markersVO = (List<MarkersVO>) redisTemplate.opsForValue().get(key);
        // 2. 判断缓存中是否有数据，有则直接返回
        if(markersVO!=null){
            return markersVO;
        }
        // 3. 缓存中没有数据，则从数据库中获取数据
        // 3.1 获取互斥锁
        String lockKey = "lock:"+level+":"+code;
        boolean isLock = tryLock(lockKey);
        // 3.2 判断是否获取锁成功
        try {
            if(!isLock){
                // 3.3 失败，则休眠并再次尝试获取锁
                Thread.sleep(50);
                return getTreeMarkers(level, code);
            }

            // 3.4 获取锁成功，判断redis中是否有数据
            markersVO = (List<MarkersVO>) redisTemplate.opsForValue().get(key);
            if(markersVO!=null){
                return markersVO;
            }
            // 3.5 若缓存中还是没有数据就查询数据库
            markersVO = treeMapper.selectTreeMarkers(level, code);
            // 模拟重建的延时
            //Thread.sleep(200);

            // 4. 数据库中也没有数据，则将空数据写入redis
            if(markersVO==null){
                redisTemplate.opsForValue().set(key,new ArrayList<>(), 20, TimeUnit.SECONDS);
            }

            // 5. 存在，数据写入redis
            redisTemplate.opsForValue().set(key, markersVO, 1000, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 6. 释放锁
            unlock(lockKey);
        }

        // 7. 返回数据
        return markersVO;
    }
/*    @Override
    public List<MarkersVO> getTreeMarkers(String level, String code) {
        List<MarkersVO> markersVO = treeMapper.selectTreeMarkers(level, code);
        return markersVO;
    }*/



    /**
     * 获取古树详情信息
     *
     * @param treeId
     * @return
     */
    @Transactional
    @Override
    public TreeDetailVO getDetailTreeInfo(Integer treeId) {
        TreeDetailVO treeDetailVO = (TreeDetailVO) redisTemplate.opsForValue().get("tree:"+treeId);
        if(treeDetailVO!=null){
            return treeDetailVO;
        }
        // 获取古树详情信息
        AncientTree ancientTree = treeMapper.selectTreeDetail(treeId);
        if(ancientTree==null){
            return null;
        }
         treeDetailVO = new TreeDetailVO();
        //古树图片
        List<TreeMedia> treeMediaList = treeDetailMapper.selectTreeImages(treeId);
        treeDetailVO.setMediaList(treeMediaList);
        BeanUtils.copyProperties(ancientTree, treeDetailVO);
        redisTemplate.opsForValue().set("tree:"+treeId, treeDetailVO, 1000, TimeUnit.MINUTES);
        return treeDetailVO;
    }

    /**
     * 获取古树统计信息
     *
     * @param level
     * @param code
     * @return
     */
    @Override
    public TreeStatisticsVO getTreeStatistics(String level, String code) {
        TreeStatisticsVO treeStatisticsVO = new TreeStatisticsVO();
        List<TreeGenusCountVO> treeGenusCountVOList = treeMapper.selectTreeGenusCount(level, code);
        List<TreeFamilyCountVO> treeFamilyCountVOList = treeMapper.selectTreeFamilyCount(level, code);
        List<TreeProtectionCountVO> treeProtectionCountVOList =treeMapper.selectTreeProtectionCount(level, code);
        treeStatisticsVO.setGenusCountList(treeGenusCountVOList);
        treeStatisticsVO.setFamilyCountList(treeFamilyCountVOList);
        treeStatisticsVO.setProtectionCountList(treeProtectionCountVOList);
        return treeStatisticsVO;
    }

    /**
     * 获取古树列表
     *
     * @param treePageDTO
     * @return
     */
    @Override
    public PageResult<TreeListVO> getTreeInfo(TreePageDTO treePageDTO) {
        PageHelper.startPage(treePageDTO.getPageNum(), treePageDTO.getPageSize());
         Page<TreeListVO> pages= treeMapper.selectTreeInfo(treePageDTO);
         PageResult<TreeListVO> pageResult = new PageResult<>(pages.getTotal(), pages.getResult());
        return pageResult;
    }

    @Override
    @Transactional
    public void delete(Integer[] ids) {
        // 删除古树信息
        treeMapper.delete(ids);
        // 删除树木图片信息
        treeDetailMapper.deleteByTreeId(ids);
        // 删除Redis中的数据

    }

    // 尝试获取锁
    private boolean tryLock(String key){
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1");
        //防止空指针异常
        return BooleanUtils.toBoolean(flag);
    }
    // 释放锁
    private void unlock(String key){
        redisTemplate.delete(key);
    }

}

